package net.kelissa.divizor.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import net.kelissa.divizor.util.MathUtil;

public class DivizorModel
{
  private Map<Integer, CellModel> cells;
  private Set<Integer> fixed;
  private ClassWeight weights;
  private List<DataModel> data;

  public DivizorModel ()
  {
    this.cells = new HashMap<Integer, CellModel>();
    this.fixed = new HashSet<Integer>();
    this.weights = new ClassWeight();
    this.data = new ArrayList<DataModel>();
  }

  @Override
  public DivizorModel clone()
  {
    DivizorModel divizorModel = new DivizorModel();

    List<DataModel> newData = new ArrayList<DataModel>();
    newData.addAll(this.data);
    divizorModel.setData(newData);

    divizorModel.setWeights(this.weights);

    HashSet<Integer> newFixed = new HashSet<Integer>();
    newFixed.addAll(this.fixed);
    divizorModel.setFixed(newFixed);

    Map<Integer, CellModel> newCells = new HashMap<Integer, CellModel>();
    for (Entry<Integer, CellModel> entry : this.cells.entrySet())
    {
      CellModel newCell = entry.getValue().clone();
      newCells.put(newCell.getId(), newCell);
    }
    divizorModel.setCells(newCells);
    return divizorModel;
  }

  public void put(Integer id, CellModel cell)
  {
    this.cells.put(id, cell);
  }


  private void addAll(Set<Integer> fixed)
  {
    this.fixed.addAll(fixed);
  }

  public void putAll(Map<Integer, CellModel> cells)
  {
    this.cells.putAll(cells);
  }
  public void putAll(List<DataModel> dataList)
  {
    this.data.addAll(dataList);
  }

  public void remove(int cellId)
  {
    this.cells.remove(cellId);

  }

  public void removeAll(Map<Integer, CellModel> neighbors)
  {
    for (Entry<Integer, CellModel> entry : neighbors.entrySet())
    {
      this.cells.remove(entry.getKey());
    }
  }

  public void removeAll(List<DataModel> dataList)
  {
    for (DataModel dataModel : dataList)
    {
      this.data.remove(dataModel);
    }
  }


  public List<DataModel> ownData(Map<Integer, CellModel> neighbors)
  {
    List<DataModel> dataList = new ArrayList<DataModel>();

    for (DataModel dataModel : this.data)
    {
      int neighbor = MathUtil.neighbor(this.cells, dataModel);
      if (neighbors.containsKey(neighbor))
      {
        dataList.add(dataModel);
      }
    }
    return dataList;
  }


  public Map<Integer, CellModel> neighbors(List<DataModel> dataList)
  {
    Map<Integer, CellModel> neighbors = new HashMap<Integer, CellModel>();
    for (DataModel dataModel : dataList)
    {
      int neighbor = MathUtil.neighbor(this.cells, dataModel);
      neighbors.put( neighbor, this.cells.get(neighbor));
    }
    return neighbors;
  }

  public void paste(DivizorModel newPiece)
  {
    this.putAll(newPiece.getCells());
    this.putAll(newPiece.getData());
    this.addAll(newPiece.getFixed());

  }


  public DivizorModel cut(int cellId, int deep)
  {
    DivizorModel clone = this.clone();
    DivizorModel piece = new DivizorModel();

    Map<Integer, CellModel> neighbors = new HashMap<Integer, CellModel>();
    neighbors.put(cellId, clone.getCell(cellId));

    List<DataModel> dataList = clone.ownData(neighbors);

    for (int i = 0; i <= deep; i++)
    {
      if(clone.getCells().size()== 0)
        break;
      neighbors = clone.neighbors( dataList);
      piece.putAll(neighbors);
      dataList = clone.ownData(neighbors);
      clone.removeAll(neighbors);
    }
    //Fixed
    if(clone.getCells().size() > 0)
    {
      neighbors = clone.neighbors( dataList);
      dataList = clone.ownData(neighbors);
      piece.putAll(neighbors);
      piece.setFixed(neighbors.keySet());
    }
    piece.putAll(dataList);
    this.cut(piece);
    return piece;
  }


  private DivizorModel cut(DivizorModel piece)
  {
    this.removeAll(piece.getCells());
    this.removeAll(piece.getData());
    this.setFixed(new HashSet<Integer>());
    return this;
  }


  public DivizorModel perturbation()
  {
    Map<Integer, List<DataModel>> groups = MathUtil.calcGroups(this);
    for (Entry<Integer, CellModel> entry : this.cells.entrySet())
    {
      Integer key = entry.getKey();
      List<DataModel> list = groups.get(key);
      if( !this.fixed.contains(key) && list != null && list.size()>0)
      {
        CellModel cell = entry.getValue();
        cell.perturbation(list);
      }
    }
    MathUtil.calcGroups(this);
    for (Entry<Integer, CellModel> entry : this.cells.entrySet())
    {
      CellModel cell = entry.getValue();
      cell.recalcDivizor(groups.get(cell.getId()), this.weights);
    }
    return this;
  }

  public void mutate()
  {
    // TODO Auto-generated method stub

  }


  // SETTERS AND GETTERS
  public CellModel getCell(int id)
  {
    return this.cells.get(id);
  }

  public Map<Integer, CellModel> getCells()
  {
    return cells;
  }
  public void setCells(Map<Integer, CellModel> cells)
  {
    this.cells = cells;
  }

  public ClassWeight getWeights()
  {
    return weights;
  }

  public void setWeights(ClassWeight weights)
  {
    this.weights = weights;
  }

  public List<DataModel> getData()
  {
    return data;
  }

  public void setData(List<DataModel> data)
  {
    this.data = data;
  }

  public Set<Integer> getFixed()
  {
    return fixed;
  }

  public void setFixed(Set<Integer> fixed)
  {
    this.fixed = fixed;
  }

  public DivizorModel(Map<Integer, CellModel> cells, ClassWeight weights, List<DataModel> data)
  {
    super();
    this.cells = cells;
    this.weights = weights;
    this.data = data;
  }

  public DivizorModel(Map<Integer, CellModel> cells, Set<Integer> fixed, ClassWeight weights, List<DataModel> data)
  {
    super();
    this.cells = cells;
    this.fixed = fixed;
    this.weights = weights;
    this.data = data;
  }



}
